package com.molichuxing.framework.utils;

import com.molichuxing.framework.exception.RedisLockException;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;

/**
 * redis分布式锁
 *
 * @Author zoumingyu
 * @Date 2020年05月12日
 */
public class RedisLock implements Lock {

    /**
     * 加锁成功
     */
    private static final String LOCK_SUCCESS = "OK";
    /**
     * 解锁成功
     */
    private static final Long UNLOCK_SUCCESS = 1L;
    /**
     * 解锁 lua 脚本
     */
    private static final String UNLOCK_SCRIPT = "if redis.call(\"get\",KEYS[1]) == " +
            "ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end";


    private String clientId = UUID.randomUUID().toString();
    private RedisUtil redisUtil;
    private String lockKey;
    private int seconds;

    private RedisLock() {
    }

    /**
     * 构造方法
     *
     * @param redisUtil redisUtil
     * @param key       key
     * @param timeout   过期时间，单位是毫秒。
     */
    public RedisLock(RedisUtil redisUtil, String key, int timeout) {
        if (key == null || (key = key.trim()).length() == 0) {
            throw new RedisLockException("Redis锁的Key不能为空");
        }
        if (timeout <= 0) {
            throw new RedisLockException("Redis锁的超时时间必须大于0");
        }
        this.redisUtil = redisUtil;
        this.lockKey = key;
        this.seconds = timeout;
    }

    @Override
    public void lock() {
        try {
            doLock(0L, false);
        } catch (InterruptedException e) {
            throw new RedisLockException("获取Redis锁被中断");
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        doLock(0L, true);
    }

    @Override
    public boolean tryLock() {
        String result = redisUtil.set(lockKey, clientId, RedisUtil.RedisNxxx.NX, RedisUtil.RedisExpx.PX, seconds);
        return LOCK_SUCCESS.equalsIgnoreCase(result);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return doLock(unit.toNanos(time), true);
    }

    @Override
    public void unlock() {
        Long result = redisUtil.eval(UNLOCK_SCRIPT, Arrays.asList(lockKey), Arrays.asList(clientId));
//        long result = redisUtil.del(lockKey);
//        if (UNLOCK_SUCCESS != result) {
//            // 解锁失败
//
//        }
    }

    public boolean doLock(long waitNanos, boolean interruptibly) throws InterruptedException {
        long deadline = waitNanos <= 0 ? Long.MAX_VALUE : System.nanoTime() + waitNanos;
        while (!tryLock()) {
            if (deadline > System.nanoTime()) {
                LockSupport.parkNanos(100000000L);// 等100毫秒
                if (interruptibly && Thread.interrupted()) {
                    throw new InterruptedException();
                }
            } else {
                return false;
            }
        }
        return true;
    }

    @Override
    public Condition newCondition() {
        throw new UnsupportedOperationException("Redis分布式锁不支持newCondition操作");
    }
}
